#include <libdswutility/dswupnpclient.h>

CDswupnpclient::CDswupnpclient(LPST_ENV_T pEnv)
: CDswbase(pEnv)
, m_thread_id(INVALID_PTHREAD_ID)
, m_nNatType(NatUnKnow)
{
	LOG4CPLUS_DEBUG(log_dsw, "Create CDswupnpclient class!");
	pthread_mutex_init(&m_nLock, NULL);
	pthread_create(&m_thread_id, NULL, CDswupnpclient::cbUpnpThreadFunc, (void *)this);
}

CDswupnpclient::~CDswupnpclient()
{
	if (m_thread_id != INVALID_PTHREAD_ID)
		pthread_join(m_thread_id, NULL);
	pthread_mutex_destroy(&m_nLock);
	LOG4CPLUS_DEBUG(log_dsw, "Destroy CDswupnpclient class!");
}

void CDswupnpclient::UpnpThreadFunc()
{
	LOG4CPLUS_DEBUG(log_dsw, "Start upnp thread!");
	ST_DEVICE_BASE_T nBaseParam;
	SendMessage(PMSG_GET_DEVICE_BASE_PARAM, (void*)&nBaseParam);
	while(!m_pEnv->bExit){
		int nAVPort = 0;
		SendMessage(PMSG_GET_VIDEOLISTEN_PORT, (void*)&nAVPort);
		if (nAVPort <= 0){
			int ndelay10s = 0;
			while(!m_pEnv->bExit){
				usleep(1000 * 100);
				ndelay10s++;
				if ((ndelay10s) % 100 == 0){
					ndelay10s = 0;
					break;
				}
			}
			continue;
		}
		struct UPNPDev *devlist;
		struct UPNPUrls urls;
		struct IGDdatas data;
		int error, ndelay10s = 0;
		char lanaddr[64];
		while(!m_pEnv->bExit){
			devlist = upnpDiscover(1000 * 5, NULL, NULL, 0, 0, &error);
			if ((devlist == NULL) || m_pEnv->bExit)
				goto DELAY_1S;
			
			int nRet;
			nRet = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
			if ((nRet != 1) || m_pEnv->bExit)
				LOG4CPLUS_WARN(log_dsw, "UPNP_GetValidIGD return invalid value, value = " << nRet);
			else{
//				printf("controlURL = %s\n", urls.controlURL);
//				printf("ipcondescURL = %s\n", urls.ipcondescURL);
//				printf("controlURL_CIF = %s\n", urls.controlURL_CIF);
//				printf("controlURL_6FC = %s\n", urls.controlURL_6FC);
//				printf("rootdescURL = %s\n", urls.rootdescURL);
				
				char sAVPort[16];
				sprintf(sAVPort, "%d", nAVPort);
				if (!SetRedirectAndTest(&urls, &data, lanaddr, sAVPort, sAVPort, "TCP", "0", nBaseParam.devid, 0)){
					MUTEX_LOCK(&m_nLock);
					m_nNatType = NatUnKnow;
					MUTEX_UNLOCK(&m_nLock);
				}
			}
			FreeUPNPUrls(&urls);
			freeUPNPDevlist(devlist);

			while(!m_pEnv->bExit){
				usleep(1000 * 100);
				ndelay10s++;
				if (((ndelay10s) % (10 * 30)) == 0){
					ndelay10s = 0;
					break;
				}
			}
			ndelay10s = 0;

DELAY_1S:
			while(!m_pEnv->bExit){
				usleep(1000 * 100);
				ndelay10s++;
				if ((ndelay10s) % 10 == 0){
					ndelay10s = 0;
					break;
				}
			}
		}
	}
	LOG4CPLUS_DEBUG(log_dsw, "Exit upnp thread!");
}

void CDswupnpclient::RemovePortMap(struct UPNPUrls * urls, struct IGDdatas * data, char* pDevId)
{
	int r;
	char index[6];
	char intClient[40];
	char intPort[6];
	char extPort[6];
	char protocol[4];
	char desc[80];
	char enabled[6];
	char rHost[64];
	char duration[16];
	int i = 0;
	do{
		snprintf(index, 6, "%d", i);
		rHost[0] = '\0'; enabled[0] = '\0';
		duration[0] = '\0'; desc[0] = '\0';
		extPort[0] = '\0'; intPort[0] = '\0'; intClient[0] = '\0';
		r = UPNP_GetGenericPortMappingEntry(urls->controlURL, data->first.servicetype, index, extPort, intClient, intPort, 
			protocol, desc, enabled, rHost, duration);
		if ((r == UPNPCOMMAND_SUCCESS) && (strcmp(desc, pDevId) == 0)){
			UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, intPort, "TCP", NULL);
			UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, intPort, "UDP", NULL);
			LOG4CPLUS_DEBUG(log_dsw, "Delete an port map rules on " << intClient << ":" << intPort);
			break;
		}
		i++;
	}while((r == UPNPCOMMAND_SUCCESS) && (!m_pEnv->bExit));
}

void CDswupnpclient::ListPortMap(struct UPNPUrls * urls, struct IGDdatas * data)
{
	int r;
	int i = 0;
	struct PortMappingParserData pdata;
	struct PortMapping * pm;

	memset(&pdata, 0, sizeof(struct PortMappingParserData));
	r = UPNP_GetListOfPortMappings(urls->controlURL,
                                   data->first.servicetype,
	                               "0",
	                               "65535",
	                               "TCP",
	                               "1000",
	                               &pdata);
	if(r == UPNPCOMMAND_SUCCESS)
	{
		printf(" i protocol exPort->inAddr:inPort description remoteHost leaseTime\n");
		for(pm = pdata.l_head; pm != NULL; pm = pm->l_next)
		{
			printf("%2d %s %5hu->%s:%-5hu '%s' '%s' %u\n",
			       i, pm->protocol, pm->externalPort, pm->internalClient,
			       pm->internalPort,
			       pm->description, pm->remoteHost,
			       (unsigned)pm->leaseTime);
			i++;
		}
		FreePortListing(&pdata);
	}
	else
		printf("xxxxxxxxxxx = %d\n", r);
}

bool CDswupnpclient::SetRedirectAndTest(struct UPNPUrls * urls,
	struct IGDdatas * data,
	const char * iaddr,
	const char * iport,
	const char * eport,
	const char * proto,
	const char * leaseDuration,
	const char * description,
	int addAny)
{
	char externalIPAddress[40];
	char intClient[40];
	char intPort[6];
	char reservedPort[6];
	char duration[16];
	int r;

	if (!iaddr || !iport || !eport || !proto || m_pEnv->bExit){
		LOG4CPLUS_ERROR(log_dsw, "Wrong arguments!");
		return false;
	}
	if (!proto || m_pEnv->bExit){
		LOG4CPLUS_ERROR(log_dsw, "invalid protocol!");
		return false;
	}
	r = UPNP_GetExternalIPAddress(urls->controlURL, data->first.servicetype, externalIPAddress);
	if ((r != UPNPCOMMAND_SUCCESS) || m_pEnv->bExit){
		LOG4CPLUS_ERROR(log_dsw, "GetExternalIPAddress failed!");
		return false;
	}
	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
		 data->first.servicetype,
		 eport, proto, NULL/*remoteHost*/,
		 intClient, intPort, NULL/*desc*/,
		 NULL/*enabled*/, duration);
	if ((r == UPNPCOMMAND_SUCCESS) && !m_pEnv->bExit){
		unsigned long nIp;
		char sWanIp[64], sLanIp[64];
		SendMessage(PMSG_GET_ACTIVE_IP, (void*)&nIp);
		strcpy(sLanIp, inet_ntoa(*(struct in_addr *)&nIp));
		if ((strcmp(sLanIp, intClient) != 0) || (strcmp(intPort, iport) != 0)){
			LOG4CPLUS_DEBUG(log_dsw, "Ipaddr has been change, should delete map " << intClient << ":" << intPort);
			r = UPNP_DeletePortMapping(urls->controlURL, data->first.servicetype, eport, proto, NULL);
			if (r == UPNPCOMMAND_SUCCESS)
				LOG4CPLUS_DEBUG(log_dsw, "Delete an port map rules on " << intClient << ":" << intPort);
			else
				LOG4CPLUS_ERROR(log_dsw, "Delete an port map rules on " << intClient << ":" << intPort << ", error!");
		}
		else{
			SendMessage(PMSG_GET_WANIP, (void*)sWanIp);
			if (strcmp(sWanIp, externalIPAddress) == 0){
				LOG4CPLUS_DEBUG(log_dsw, "external " << externalIPAddress <<
					":" << eport << " " << proto << " is redirected to internal " << intClient << ":" << intPort <<
					" (duration=" << duration << ")!");
				MUTEX_LOCK(&m_nLock);
				m_nNatType = NatUPNP;
				MUTEX_UNLOCK(&m_nLock);
			}
			else{
				MUTEX_LOCK(&m_nLock);
				m_nNatType = NatUnKnow;
				MUTEX_UNLOCK(&m_nLock);
			}
			return true;
		}
	}
	if (addAny && !m_pEnv->bExit) {
		r = UPNP_AddAnyPortMapping(urls->controlURL, data->first.servicetype,
					   eport, iport, iaddr, description,
					   proto, 0, leaseDuration, reservedPort);
		if (r == UPNPCOMMAND_SUCCESS)
			eport = reservedPort;
		else{
			LOG4CPLUS_ERROR(log_dsw, "AddAnyPortMapping(" << eport << ", " << iport << ", " << iaddr <<
				") failed with code " << r << " (" << strupnperror(r) << ")!");
			return false;
		}
	} else if (!m_pEnv->bExit){
		r = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype,
			eport, iport, iaddr, description, proto, 0, leaseDuration);
		if (r != UPNPCOMMAND_SUCCESS){
			LOG4CPLUS_ERROR(log_dsw, "AddPortMapping(" << eport << ", " << iport << ", " << iaddr <<
				") failed with code " << r << " (" << strupnperror(r) << ")!");
			return false;
		}
	}
	if (m_pEnv->bExit)
		return false;
	r = UPNP_GetSpecificPortMappingEntry(urls->controlURL,
		 data->first.servicetype,
		 eport, proto, NULL/*remoteHost*/,
		 intClient, intPort, NULL/*desc*/,
		 NULL/*enabled*/, duration);
	if (r != UPNPCOMMAND_SUCCESS){
		LOG4CPLUS_ERROR(log_dsw, "GetSpecificPortMappingEntry() failed with code " << r << " (" << strupnperror(r) << ")!");
		return false;
	}
	else {
		char sWanIp[64];
		SendMessage(PMSG_GET_WANIP, (void*)sWanIp);
		if (strcmp(sWanIp, externalIPAddress) == 0){
			LOG4CPLUS_DEBUG(log_dsw, "external " << externalIPAddress <<
				":" << eport << " " << proto << " is redirected to internal " << intClient << ":" << intPort <<
				" (duration=" << duration << ")!");
			MUTEX_LOCK(&m_nLock);
			m_nNatType = NatUPNP;
			MUTEX_UNLOCK(&m_nLock);
		}
		else{
			MUTEX_LOCK(&m_nLock);
			m_nNatType = NatUnKnow;
			MUTEX_UNLOCK(&m_nLock);
		}
	}
	return true;
}

int CDswupnpclient::OnMessage(EN_MSG_TYPE nMsgType, void * pParam)
{
	switch(nMsgType){
	case PMSG_GET_NATTYPE:
		MUTEX_LOCK(&m_nLock);
		*((NAT_TYPE_E*)pParam) = m_nNatType;
		MUTEX_UNLOCK(&m_nLock);
		break;
	}
	return 0;
}

